using System;
using System.Collections.Generic;
using System.Linq;
using Baula.TextGen.ActiveTemplating.Tokenizer;
using Baula.TextGen.ActiveTemplating.Tokenizer.Tokens;

namespace Baula.TextGen.ActiveTemplating
{
    public class TemplateToVBConverter
    {
        private readonly IDictionary<string, string> _wellKnownExpressions = new Dictionary<string, string>();
        private readonly List<string> _lines = new List<string>();
        private bool _previousTokenWasBlockExpression;
        private bool _nextTokenIsBlockExpression;
        private string _currentLine;
        private bool _noNewLineAfterCurrentLine;

        public virtual string Convert(string template)
        {
            if (string.IsNullOrEmpty(template))
                return string.Empty;

            var tokens = new TemplateTokenizer(template).Tokenize();
            new TokensDumper().Dump(tokens);
            var isBlockToken = false;
            var tokenEnumerator = new ReadAheadEnumerator<IToken>(tokens);
            while (tokenEnumerator.MoveNext())
            {
                var currentToken = tokenEnumerator.Current;
                _previousTokenWasBlockExpression = isBlockToken;
                _nextTokenIsBlockExpression = tokenEnumerator.NextItem is BlockExpressionToken;
                isBlockToken = currentToken is BlockExpressionToken;
                if (isBlockToken)
                {
                    StartNewBlock();
                }
                AppendToCurrentBlock(currentToken);
            }
            if (!isBlockToken)
                EndCurrentLine();

            return string.Join("\r\n", _lines);
        }

        private void AppendToCurrentBlock(IToken token)
        {
            if (token is EofToken)
                return;

            Append(token as LiteralToken);
            Append(token as InlineExpressionToken);
            Append(token as BlockExpressionToken);
        }

        private void Append(LiteralToken literalToken)
        {
            if (literalToken == null)
                return;

            var literal = literalToken.Literal;
            var lines = literal.Split(new[] { "\r\n" }, StringSplitOptions.None);
            if (_previousTokenWasBlockExpression)
            {
                lines = lines.SkipWhile((l, i) => l == string.Empty && i == 0).ToArray();
            }
            _noNewLineAfterCurrentLine = false;
            if (_nextTokenIsBlockExpression)
            {
                var count = lines.Length;
                //lines = lines.TakeWhile((l, i) => !(l == string.Empty && i == count - 1)).ToArray();
                if (lines[count-1] == string.Empty)
                {
                    lines = lines.Take(count - 1).ToArray();
                }
                else
                {
                    _noNewLineAfterCurrentLine = true;
                }
            }
            for (var index = 0; index < lines.Length; index++)
            {
                var line = lines[index];
                var literalForArgument = string.Empty;
                if (!string.IsNullOrEmpty(line))
                {
                    literalForArgument = string.Format("\"{0}\"", line.Replace("\"", "\"\""));
                }
                AddToCurrentLine(literalForArgument);
                if (index < lines.Length - 1)
                    EndCurrentLine();
            }
        }

        private void Append(InlineExpressionToken inlineToken)
        {
            if (inlineToken == null)
                return;

            AddToCurrentLine(GetCode(inlineToken));
        }

        private void Append(BlockExpressionToken blockToken)
        {
            if (blockToken == null)
                return;

            var lines = blockToken.Expression.Split(new[] {"\r\n"}, StringSplitOptions.None);
            for (int index = 0; index < lines.Length; index++)
            {
                var line = lines[index];
                _lines.Add(line);
            }
        }


        private void EndCurrentLine()
        {
            if (_currentLine != null)
            {
                var format = _noNewLineAfterCurrentLine ? "Append({0})" : "AppendLine({0})";
                _lines.Add(string.Format(format, _currentLine));
                _currentLine = string.Empty;
            }
        }

        private void AddToCurrentLine(string text)
        {
            if (string.IsNullOrEmpty(_currentLine))
            {
                _currentLine = text;
            }
            else
            {
                if (!string.IsNullOrEmpty(text))
                {
                    _currentLine += " & " + text;
                }
            }
        }

        private void StartNewBlock()
        {
            EndCurrentLine();
        }

        // TODO: move this to InlineExpressionToken
        public void AddWellknownExpression(string expression, string code)
        {
            if (code == null)
                throw new ArgumentNullException("code");

            _wellKnownExpressions.Add(expression, code);
        }

        private string GetCode(InlineExpressionToken inlineExpression)
        {
            if (!_wellKnownExpressions.ContainsKey(inlineExpression.Expression))
                return inlineExpression.Expression;

            var code = _wellKnownExpressions[inlineExpression.Expression];
            if (code == null)
                throw new Exception(string.Format("The code for thw well-known expression \"{0}\" is not set.", inlineExpression.Expression));

            return code;
        }
    }
}